package container

import (
	"github.com/sirupsen/logrus"
	"mydocker/common"
	"mydocker/utils"
	"os"
	"os/exec"
	"path"
)

// NewWorkSpace 创建一个 overlay2 的文件系统作为容器的工作目录
/*
1）创建 lower 层
2）创建 upper、worker、merged 层
3）挂载 overlayFS
4）如果有指定 volume 则挂载 volume（容器中将会挂载在 merged 目录下，即容器的工作目录下）
*/
func NewWorkSpace(volume, containerID, imageName string) error {
	// 1. 创建 lower 层
	err := createLower(containerID, imageName)
	if err != nil {
		logrus.Errorf("create lower err: %v", err)
		return err
	}
	// 2. 创建 upper、worker、merged 层
	err = createDirs(containerID)
	if err != nil {
		logrus.Errorf("create dirs err: %v", err)
		return err
	}
	// 3. 挂载 overlayFS
	err = mountOverlayFS(containerID)
	if err != nil {
		logrus.Errorf("overlay mount, err: %v", err)
		return err
	}
	// 4. 如果指定了 volume 则还需 mount volume
	if volume != "" {
		mergedPath := utils.GetMerged(containerID)
		hostPath, containerPath, err := volumeExtract(volume)
		if err != nil {
			logrus.Errorf("extract volume failed，maybe volume parameter input is not correct，detail:%v", err)
			return err
		}
		mountVolume(mergedPath, hostPath, containerPath)
	}
	return nil
}

// createLower 根据 containerID, imageName 准备 lower 目录
func createLower(containerID, imageName string) error {
	// 根据 containerID 拼接出 lower 目录
	// 根据 imageName 找到镜像 tar，并解压到 lower 目录中
	lowerPath := utils.GetLower(containerID)
	imagePath := utils.GetImage(imageName)
	logrus.Infof("lower:%s image.tar:%s", lowerPath, imagePath)
	// 检查目录是否已经存在
	exists, err := utils.PathExists(lowerPath)
	if err != nil {
		logrus.Errorf("Fail to judge whether dir %s exists. %v", lowerPath, err)
		return err
	}
	// 不存在则创建目录并将 image.tar 解压到 lower 文件夹中
	if !exists {
		if err = os.MkdirAll(lowerPath, common.Perm0777); err != nil {
			logrus.Errorf("Mkdir dir %s error. %v", lowerPath, err)
			return err
		}
		if _, err = exec.Command("tar", "-xvf", imagePath, "-C", lowerPath).CombinedOutput(); err != nil {
			logrus.Errorf("Untar dir %s error. %v", lowerPath, err)
			return err
		}
	}
	return nil
}

// createDirs 创建 overlayFS 需要的 merged、upper、worker 目录
func createDirs(containerID string) error {
	dirs := []string{
		utils.GetMerged(containerID),
		utils.GetUpper(containerID),
		utils.GetWork(containerID),
	}

	for _, dir := range dirs {
		if err := os.MkdirAll(dir, common.Perm0777); err != nil {
			logrus.Errorf("Mkdir dir %s error. %v", dir, err)
			return err
		}
	}
	return nil
}

// mountOverlayFS 挂载 overlayFS
func mountOverlayFS(containerID string) error {
	// 拼接参数
	// eg：lowerdir=/root/busybox,upperdir=/root/upper,workdir=/root/work
	dirs := utils.GetOverlayFSDirs(utils.GetLower(containerID), utils.GetUpper(containerID), utils.GetWork(containerID))
	mergedPath := utils.GetMerged(containerID)
	// 完整命令：mount -t overlay overlay -o lowerdir=/root/{containerID}/lower,upperdir=/root/{containerID}/upper,workdir=/root/{containerID}/work /root/{containerID}/merged
	cmd := exec.Command("mount", "-t", "overlay", "overlay", "-o", dirs, mergedPath)
	logrus.Infof("mount overlayfs: [%s]", cmd.String())
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		logrus.Errorf("mnt cmd run, err: %v", err)
		return err
	}
	return nil
}

// DeleteWorkSpace 退出容器时删除 UFS 文件系统
/*
和创建相反
1.有 volume 则卸载 volume
2.卸载并移除 merged 目录
3.卸载并移除 upper、worker 层
*/
func DeleteWorkSpace(containerID, volume string) {
	// 如果指定了 volume 则需要 umount volume
	// NOTE：一定要先 umount volume，然后再删除目录，否则由于 bind mount 存在，删除临时目录会导致 volume 目录中的数据丢失。
	if volume != "" {
		_, containerPath, err := volumeExtract(volume)
		if err != nil {
			logrus.Errorf("extract volume failed，maybe volume parameter input is not correct，detail:%v", err)
			return
		}
		mntPath := utils.GetMerged(containerID)
		umountVolume(mntPath, containerPath)
	}
	umountOverlayFS(containerID)
	deleteDirs(containerID)
}

func umountOverlayFS(containerID string) {
	mntPath := utils.GetMerged(containerID)
	cmd := exec.Command("umount", mntPath)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	logrus.Infof("umountOverlayFS, cmd:%v", cmd.String())
	if err := cmd.Run(); err != nil {
		logrus.Errorf("%v", err)
	}
}

func deleteDirs(containerID string) {
	dirs := []string{
		utils.GetMerged(containerID),
		utils.GetUpper(containerID),
		utils.GetWork(containerID),
		utils.GetLower(containerID),
		utils.GetRoot(containerID), // root 目录也要删除
	}

	for _, dir := range dirs {
		if err := os.RemoveAll(dir); err != nil {
			logrus.Errorf("remove dir %s error. %v", dir, err)
		}
	}
}

func umountVolume(mntPath, containerPath string) {
	// mntPath 为容器在宿主机上的挂载点，例如 /root/merged
	// containerPath 为 volume 在容器中对应的目录，例如 /root/tmp
	// containerPathInHost 则是容器中目录在宿主机上的具体位置，例如 /root/merged/root/tmp
	containerPathInHost := path.Join(mntPath, containerPath)
	cmd := exec.Command("umount", containerPathInHost)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		logrus.Errorf("Umount volume failed. %v", err)
	}
}
